﻿//#define TESTREAD

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
#if NETFX_CORE
using Windows.Storage.Streams;
#endif

namespace Microsoft.Media.ISO
{
    public static class MP4BoxReader
    {
        static IDictionary<string, Func<BoxBinaryReader, ulong, IBox>> boxReaders;
        static IDictionary<Guid, Func<BoxBinaryReader, ulong, IUniversalUniqueIdentifierBox>> extendedBoxReaders;

        static MP4BoxReader()
        {
            extendedBoxReaders = new Dictionary<Guid, Func<BoxBinaryReader, ulong, IUniversalUniqueIdentifierBox>>();
            extendedBoxReaders.Add(new Guid("6d1d9b05-42d5-44e6-80e2-141daff757b2"), ReadTrackFragmentExtendedHeaderBox);
            extendedBoxReaders.Add(new Guid("a5d40b30-e814-11dd-ba2f-0800200c9a66"), ReadLiveServerManifestBox);

            boxReaders = new Dictionary<string, Func<BoxBinaryReader, ulong, IBox>>();
            boxReaders.Add("ftyp", ReadFileTypeBox);
            boxReaders.Add("uuid", ReadUniversalUniqueIdentifierBox);
            boxReaders.Add("moov", ReadMovieBox);
            boxReaders.Add("mdat", ReadMediaDataBox);
            boxReaders.Add("mvhd", ReadMovieHeaderBox);
            boxReaders.Add("trak", ReadTrackBox);
            boxReaders.Add("mdia", ReadMediaBox);
            boxReaders.Add("tkhd", ReadTrackHeaderBox);
            boxReaders.Add("mdhd", ReadMediaHeaderBox);
            boxReaders.Add("hdlr", ReadHandlerReferenceBox);
            boxReaders.Add("minf", ReadMediaInformationBox);
            boxReaders.Add("stss", ReadSyncSamplesBox);
            boxReaders.Add("vmhd", ReadVideoMediaHeaderBox);
            boxReaders.Add("smhd", ReadSoundMediaHeaderBox);
            boxReaders.Add("dinf", ReadDataInformationBox);
            boxReaders.Add("dref", ReadDataReferenceBox);
            boxReaders.Add("nmhd", ReadNullMediaHeaderBox);
            boxReaders.Add("udta", ReadUserDataBox);
            boxReaders.Add("meta", ReadMetaBox);
            boxReaders.Add("ilst", ReadItemListBox);
            boxReaders.Add("Xtra", ReadXtraBox);
            boxReaders.Add("url ", ReadUrlBox);
            boxReaders.Add("stbl", ReadSampleTableBox);
            boxReaders.Add("stts", ReadDecodingTimeToSampleBox);
            boxReaders.Add("ctts", ReadCompositionTimeToSampleBox);
            boxReaders.Add("stsc", ReadSampleChunkBox);
            boxReaders.Add("stco", ReadSampleChunkOffsetBox);
            boxReaders.Add("stsz", ReadSampleSizeBox);
            boxReaders.Add("stsd", ReadSampleDescriptionBox);

            boxReaders.Add("soun", ReadAudioSampleEntryBox);
            boxReaders.Add("mp4a", ReadAudioSampleEntryBox);
            boxReaders.Add("wma ", ReadAudioSampleEntryBox);
            boxReaders.Add("ac-3", ReadAudioSampleEntryBox);
            boxReaders.Add("ec-3", ReadAudioSampleEntryBox);
            boxReaders.Add("mlpa", ReadAudioSampleEntryBox);
            boxReaders.Add("dtsc", ReadAudioSampleEntryBox);
            boxReaders.Add("dtsh", ReadAudioSampleEntryBox);
            boxReaders.Add("dtsl", ReadAudioSampleEntryBox);
            boxReaders.Add("dtse", ReadAudioSampleEntryBox);
            boxReaders.Add("samr", ReadAudioSampleEntryBox);
            boxReaders.Add("sawb", ReadAudioSampleEntryBox);
            boxReaders.Add("drms", ReadAudioSampleEntryBox);
            boxReaders.Add("alac", ReadAudioSampleEntryBox);
            boxReaders.Add("owma", ReadAudioSampleEntryBox);
            boxReaders.Add("enca", ReadAudioSampleEntryBox);

            boxReaders.Add("avc1", ReadVisualSampleEntryBox);
            boxReaders.Add("s263", ReadVisualSampleEntryBox);
            boxReaders.Add("mp4v", ReadVisualSampleEntryBox);
            boxReaders.Add("drmi", ReadVisualSampleEntryBox);
            boxReaders.Add("encv", ReadVisualSampleEntryBox);

            boxReaders.Add("esds", ReadElementaryStreamDescriptorBox);
            boxReaders.Add("avcC", ReadAdvancedVideoCodingBox);
            boxReaders.Add("moof", ReadMovieFragmentBox);
            boxReaders.Add("mfhd", ReadMovieFragmentHeaderBox);
            boxReaders.Add("traf", ReadTrackFragmentBox);
            boxReaders.Add("tfra", ReadTrackFragmentRandomAccessBox);

            boxReaders.Add("sdtp", ReadIndependentAndDisposableSamplesBox);
            boxReaders.Add("tfhd", ReadTrackFragmentHeaderBox);
            boxReaders.Add("trun", ReadTrackFragmentRunBox);
            boxReaders.Add("dfxp", ReadDfxpBox);
            boxReaders.Add("stpp", ReadSubtitleSampleEntryBox);
            boxReaders.Add("mvex", ReadMovieExtendsBox);
            boxReaders.Add("trex", ReadTrackExtendsBox);
            boxReaders.Add("mfra", ReadMovieFragmentRandomAccessBox);
            boxReaders.Add("sidx", ReadSegmentIndexBox);
            boxReaders.Add("pssh", ReadProtectionSystemSpecificHeaderBox);
            boxReaders.Add("sinf", ReadProtectedSchemeInformationBox);
            boxReaders.Add("schi", ReadSchemeInformationBox);
            boxReaders.Add("free", ReadFreeSpaceBox);
            boxReaders.Add("frma", ReadFormatBox);
            boxReaders.Add("schm", ReadSchemeTypeBox);
            boxReaders.Add("mehd", ReadMovieExtendsHeaderBox);
            boxReaders.Add("tfdt", ReadTrackFragmentBaseMediaDecodeTimeBox);
            boxReaders.Add("tenc", ReadTrackEncryptionBox);
            boxReaders.Add("ainf", ReadAssetInformationBox);
        }

#if NETFX_CORE
        public static IEnumerable<IBox> ReadAllBoxes(IInputStream inputStream)
        {
            using (var reader = new BoxBinaryReader(inputStream.AsStreamForRead(), true))
            {
                foreach (var box in ReadAllBoxes(reader)) // don't dispose of the stream, the caller may want it left open
                {
                    yield return box;
                }
            }
        }

        public static IBox ReadBox(IInputStream inputStream)
        {
            using (var reader = new BoxBinaryReader(inputStream.AsStreamForRead(), true))
            {
                return ReadBox(reader);
            }
        }
#else
        public static IBox ReadBox(Stream inputStream)
        {
            var reader = new BoxBinaryReader(inputStream);
            {
                return ReadBox(reader);
            }
        }

        public static IEnumerable<IBox> ReadAllBoxes(Stream stream)
        {
            var reader = new BoxBinaryReader(stream);
            {
                while (!reader.AtEndOfStream)
                {
                    IBox box;
                    try
                    {
                        box = ReadBox(reader);
                    }
                    catch (System.IO.EndOfStreamException)
                    {
                        break;
                    }
                    yield return box;
                }
            }
        }
#endif

        internal static IEnumerable<IBox> ReadAllBoxes(BoxBinaryReader reader)
        {
            while (!reader.AtEndOfStream)
            {
                IBox box;
                try
                {
                    box = ReadBox(reader);
                }
                catch (System.IO.EndOfStreamException)
                {
                    break;
                }
                yield return box;
            }
        }

        internal static IBox ReadBox(BoxBinaryReader reader)
        {
            IBox result = null;

            ulong offset = reader.Offset;
            ulong size = reader.ReadUInt32();
            string boxType = reader.ReadString(4);
            if (size == 1)
            {
                size = reader.ReadUInt64();
            }
            Func<BoxBinaryReader, ulong, IBox> boxReader;
            ulong bytesRead = reader.Offset - offset;
            if (boxReaders.TryGetValue(boxType, out boxReader))
            {
                result = boxReader(reader, size - bytesRead);
            }
            else
            {
                System.Diagnostics.Debug.WriteLine("Unrecognized box {0}", boxType);
                result = ReadUnknownBox(reader, size - bytesRead);
            }

            result.Type = boxType;

            bytesRead = reader.Offset - offset;
            long remaining = (long)(size - bytesRead);
            if (remaining > 0) // always read to the end of the box before returning
            {
                reader.ReadBytes((int)remaining);
                System.Diagnostics.Debug.WriteLine("{0} box not fully read", boxType);
            }
            else if (remaining < 0) throw new Exception("Box over-read");


#if TESTREAD
            reader.BaseStream.Seek((long)offset, SeekOrigin.Begin);
            var sourceBytes = reader.ReadBytes((int)size);

            using (var destStream = new MemoryStream())
            {
                using (var writer = new BoxBinaryWriter(destStream, true))
                {
                    MP4BoxWriter.WriteBox(result, writer);
                    writer.Flush();
                    destStream.Seek(0, SeekOrigin.Begin);
                    var destBytes = destStream.ToArray();
                    if (!sourceBytes.SequenceEqual(destBytes))
                    {
                        System.Diagnostics.Debug.WriteLine("Mismatched output: {0}", boxType);
                        for (int i = 0; i < sourceBytes.Length; i++)
                        {
                            if (sourceBytes[i] != destBytes[i])
                            {
                                System.Diagnostics.Debug.WriteLine("Not equal at: {0}", i);
                            }
                        }
                    }
                    else
                    {
                        System.Diagnostics.Debug.WriteLine("Success: {0}", boxType);
                    }
                }
            }
#endif

            return result;
        }

        internal static void ReadFullBox(BoxBinaryReader reader, IFullBox box)
        {
            uint versionAndFlags = reader.ReadUInt32();
            box.Version = (byte)((versionAndFlags >> 0x18) & 0xff);
            box.Flags = versionAndFlags & 0xffffff;
        }

        internal static void ReadContainerBox(BoxBinaryReader reader, IContainerBox containerBox, ulong sizeRemaining)
        {
            while (sizeRemaining > 0)
            {
                var offset = reader.Offset;

                var box = ReadBox(reader);
                containerBox.InnerBoxes.Add(box);

                var bytesRead = reader.Offset - offset;
                sizeRemaining -= bytesRead;
            }
        }

        internal static FileTypeBox ReadFileTypeBox(BoxBinaryReader reader, ulong size)
        {
            var result = new FileTypeBox();
            var offset = reader.Offset;
            result.MajorBrand = reader.ReadString(4);
            result.MinorVersion = reader.ReadUInt32();

            while (size > reader.Offset - offset)
            {
                result.CompatibleBrands.Add(reader.ReadString(4));
            }

            return result;
        }

        internal static IUniversalUniqueIdentifierBox ReadUniversalUniqueIdentifierBox(BoxBinaryReader reader, ulong size)
        {
            IUniversalUniqueIdentifierBox result;

            var offset = reader.Offset;
            var uuid = reader.ReadGuid();
            var bytesRead = reader.Offset - offset;
            var sizeRemaining = size - bytesRead;

            Func<BoxBinaryReader, ulong, IUniversalUniqueIdentifierBox> extendedBoxReader;
            if (extendedBoxReaders.TryGetValue(uuid, out extendedBoxReader))
            {
                result = extendedBoxReader(reader, sizeRemaining);
            }
            else
            {
                result = new UniversalUniqueIdentifierBox()
                {
                    Data = reader.ReadBytes((int)sizeRemaining)
                };
            }

            result.Uuid = uuid;
            return result;
        }

        internal static TrackFragmentExtendedHeaderBox ReadTrackFragmentExtendedHeaderBox(BoxBinaryReader reader, ulong size)
        {
            var result = new TrackFragmentExtendedHeaderBox();

            ReadFullBox(reader, result);

            result.FraqmentAbsoluteTime = reader.ReadUInt64();
            result.FragmentDuration = reader.ReadUInt64();

            return result;
        }

        internal static LiveServerManifestBox ReadLiveServerManifestBox(BoxBinaryReader reader, ulong size)
        {
            var result = new LiveServerManifestBox();
            var offset = reader.Offset;

            ReadFullBox(reader, result);

            result.Manifest = reader.ReadString((int)(size - (reader.Offset - offset)));

            return result;
        }

        internal static MovieBox ReadMovieBox(BoxBinaryReader reader, ulong size)
        {
            var result = new MovieBox();
            var offset = reader.Offset;

            ReadContainerBox(reader, result, size - (reader.Offset - offset));

            return result;
        }

        internal static MovieHeaderBox ReadMovieHeaderBox(BoxBinaryReader reader, ulong size)
        {
            var result = new MovieHeaderBox();

            ReadFullBox(reader, result);

            if (result.Version == 0)
            {
                result.CreationTime = reader.ReadDateTimeOffsetUtc32();
                result.ModificationTime = reader.ReadDateTimeOffsetUtc32();
                result.TimeScale = reader.ReadUInt32();
                result.Duration = reader.ReadUInt32();
            }
            else
            {
                result.CreationTime = reader.ReadDateTimeOffsetUtc64();
                result.ModificationTime = reader.ReadDateTimeOffsetUtc64();
                result.TimeScale = reader.ReadUInt32();
                result.Duration = reader.ReadUInt64();
            }

            result.Rate = reader.ReadUInt32();
            result.Volume = reader.ReadUInt16();
            reader.ReadBytes(2); // reserved 1
            reader.ReadBytes(8); // reserved 2
            for (int i = 0; i < 9; i++)
            {
                result.Matrix[i] = reader.ReadUInt32();
            }
            reader.ReadBytes(24); // predefined
            result.NextTrackId = reader.ReadUInt32();

            return result;
        }

        internal static TrackBox ReadTrackBox(BoxBinaryReader reader, ulong size)
        {
            var result = new TrackBox();
            var offset = reader.Offset;

            ReadContainerBox(reader, result, size - (reader.Offset - offset));

            return result;
        }

        internal static MediaBox ReadMediaBox(BoxBinaryReader reader, ulong size)
        {
            var result = new MediaBox();

            var offset = reader.Offset;

            ReadContainerBox(reader, result, size - (reader.Offset - offset));

            return result;
        }

        internal static TrackHeaderBox ReadTrackHeaderBox(BoxBinaryReader reader, ulong size)
        {
            var result = new TrackHeaderBox();

            ReadFullBox(reader, result);

            if (result.Version == 0)
            {
                result.CreationTime = reader.ReadDateTimeOffsetUtc32();
                result.ModificationTime = reader.ReadDateTimeOffsetUtc32();
                result.TrackId = reader.ReadUInt32();
                reader.ReadUInt32(); // reserved1
                result.Duration = reader.ReadUInt32();
            }
            else
            {
                result.CreationTime = reader.ReadDateTimeOffsetUtc64();
                result.ModificationTime = reader.ReadDateTimeOffsetUtc64();
                result.TrackId = reader.ReadUInt32();
                reader.ReadUInt32(); // reserved1
                result.Duration = reader.ReadUInt64();
            }
            reader.ReadBytes(8); // reserved2
            result.Layer = reader.ReadUInt16();
            result.AlternateGroup = reader.ReadUInt16();
            result.Volume = reader.ReadUInt16();
            reader.ReadUInt16(); // reserved3
            for (int i = 0; i < 9; i++)
            {
                result.Matrix[i] = reader.ReadUInt32();
            }
            result.Width = reader.ReadUInt32();
            result.Height = reader.ReadUInt32();

            return result;
        }

        internal static MediaHeaderBox ReadMediaHeaderBox(BoxBinaryReader reader, ulong size)
        {
            var result = new MediaHeaderBox();

            ReadFullBox(reader, result);

            if (result.Version == 0)
            {
                result.CreationTime = reader.ReadDateTimeOffsetUtc32();
                result.ModificationTime = reader.ReadDateTimeOffsetUtc32();
                result.Timescale = reader.ReadUInt32();
                result.Duration = reader.ReadUInt32();
            }
            else
            {
                result.CreationTime = reader.ReadDateTimeOffsetUtc64();
                result.ModificationTime = reader.ReadDateTimeOffsetUtc64();
                result.Timescale = reader.ReadUInt32();
                result.Duration = reader.ReadUInt64();
            }

            result.Language = reader.ReadStringFromUShort();
            result.Quality = reader.ReadUInt16();

            return result;
        }

        internal static HandlerReferenceBox ReadHandlerReferenceBox(BoxBinaryReader reader, ulong size)
        {
            var result = new HandlerReferenceBox();

            var offset = reader.Offset;

            ReadFullBox(reader, result);

            reader.ReadUInt32(); // predefined
            result.HandlerType = reader.ReadString(4);

            reader.ReadUInt32(); // reserved 0
            reader.ReadUInt32(); // reserved 1
            reader.ReadUInt32(); // reserved 2

            if (size > reader.Offset - offset)
            {
                result.Name = reader.ReadNullTerminatedString();
            }

            return result;
        }

        internal static MediaInformationBox ReadMediaInformationBox(BoxBinaryReader reader, ulong size)
        {
            var result = new MediaInformationBox();

            var offset = reader.Offset;

            ReadContainerBox(reader, result, size - (reader.Offset - offset));

            return result;
        }

        internal static SyncSamplesBox ReadSyncSamplesBox(BoxBinaryReader reader, ulong size)
        {
            var result = new SyncSamplesBox();

            ReadFullBox(reader, result);

            var count = reader.ReadUInt32();
            for (int i = 0; i < count; i++)
            {
                result.SyncTable.Add(reader.ReadUInt32());
            }

            return result;
        }

        internal static SoundMediaHeaderBox ReadSoundMediaHeaderBox(BoxBinaryReader reader, ulong size)
        {
            var result = new SoundMediaHeaderBox();

            ReadFullBox(reader, result);

            result.Balance = reader.ReadUInt32();

            return result;
        }

        internal static VideoMediaHeaderBox ReadVideoMediaHeaderBox(BoxBinaryReader reader, ulong size)
        {
            var result = new VideoMediaHeaderBox();

            ReadFullBox(reader, result);

            result.GraphicsMode = reader.ReadUInt16();
            result.Color[0] = reader.ReadUInt16();
            result.Color[1] = reader.ReadUInt16();
            result.Color[2] = reader.ReadUInt16();

            return result;
        }

        internal static DataInformationBox ReadDataInformationBox(BoxBinaryReader reader, ulong size)
        {
            var result = new DataInformationBox();

            var offset = reader.Offset;

            ReadContainerBox(reader, result, size - (reader.Offset - offset));

            return result;
        }

        internal static DataReferenceBox ReadDataReferenceBox(BoxBinaryReader reader, ulong size)
        {
            var result = new DataReferenceBox();

            var offset = reader.Offset;

            ReadFullBox(reader, result);

            var count = reader.ReadUInt32();

            ReadContainerBox(reader, result, size - (reader.Offset - offset));

            return result;
        }

        internal static NullMediaHeaderBox ReadNullMediaHeaderBox(BoxBinaryReader reader, ulong size)
        {
            var result = new NullMediaHeaderBox();

            ReadFullBox(reader, result);

            return result;
        }

        internal static UserDataBox ReadUserDataBox(BoxBinaryReader reader, ulong size)
        {
            var result = new UserDataBox();
            var offset = reader.Offset;

            ReadContainerBox(reader, result, size - (reader.Offset - offset));

            return result;
        }

        internal static MetaBox ReadMetaBox(BoxBinaryReader reader, ulong size)
        {
            var result = new MetaBox();
            var offset = reader.Offset;

            ReadFullBox(reader, result);

            ReadContainerBox(reader, result, size - (reader.Offset - offset));

            return result;
        }

        internal static AssetInformationBox ReadAssetInformationBox(BoxBinaryReader reader, ulong size)
        {
            var result = new AssetInformationBox();
            var offset = reader.Offset;

            ReadFullBox(reader, result);

            result.ProfileVersion = reader.ReadString(4);
            var bytesRemaining = size - (reader.Offset - offset);
            if (bytesRemaining > 0)
            {
                result.Apid = reader.ReadNullTerminatedString();
            }

            return result;
        }

        internal static ItemListBox ReadItemListBox(BoxBinaryReader reader, ulong size)
        {
            var result = new ItemListBox();

            if (size > 0)
            {
                result.Data = reader.ReadBytes((int)size);
            }

            return result;
        }

        internal static XtraBox ReadXtraBox(BoxBinaryReader reader, ulong size)
        {
            var result = new XtraBox();

            if (size > 0)
            {
                result.Data = reader.ReadBytes((int)size);
            }

            return result;
        }

        internal static UrlBox ReadUrlBox(BoxBinaryReader reader, ulong size)
        {
            var result = new UrlBox();

            ReadFullBox(reader, result);

            return result;
        }

        internal static SampleTableBox ReadSampleTableBox(BoxBinaryReader reader, ulong size)
        {
            var result = new SampleTableBox();

            var offset = reader.Offset;

            ReadContainerBox(reader, result, size - (reader.Offset - offset));

            return result;
        }

        internal static DecodingTimeToSampleBox ReadDecodingTimeToSampleBox(BoxBinaryReader reader, ulong size)
        {
            var result = new DecodingTimeToSampleBox();

            ReadFullBox(reader, result);

            var count = reader.ReadUInt32();
            for (int i = 0; i < count; i++)
            {
                var entry = new DecodingTimeToSampleBoxEntry()
                {
                    SampleCount = reader.ReadUInt32(),
                    SampleDelta = reader.ReadUInt32()
                };
                result.Entries.Add(entry);
            }

            return result;
        }

        internal static CompositionTimeToSampleBox ReadCompositionTimeToSampleBox(BoxBinaryReader reader, ulong size)
        {
            var result = new CompositionTimeToSampleBox();

            ReadFullBox(reader, result);

            var count = reader.ReadUInt32();
            for (int i = 0; i < count; i++)
            {
                var entry = new CompositionTimeToSampleBoxEntry()
                {
                    SampleCount = reader.ReadUInt32(),
                    SampleOffset = reader.ReadUInt32()
                };
                result.Entries.Add(entry);
            }

            return result;
        }

        internal static SampleChunkBox ReadSampleChunkBox(BoxBinaryReader reader, ulong size)
        {
            var result = new SampleChunkBox();

            ReadFullBox(reader, result);

            var count = reader.ReadUInt32();
            for (int i = 0; i < count; i++)
            {
                var entry = new SampleChunkBoxEntry()
                {
                    FirstChunk = reader.ReadUInt32(),
                    SamplesPerChunk = reader.ReadUInt32(),
                    SampleDescIndex = reader.ReadUInt32()
                };
                result.Entries.Add(entry);
            }

            return result;
        }

        internal static SampleChunkOffsetBox ReadSampleChunkOffsetBox(BoxBinaryReader reader, ulong size)
        {
            var result = new SampleChunkOffsetBox();

            ReadFullBox(reader, result);

            var count = reader.ReadUInt32();
            for (int i = 0; i < count; i++)
            {
                result.Offsets.Add(reader.ReadUInt32());
            }

            return result;
        }

        internal static SampleSizeBox ReadSampleSizeBox(BoxBinaryReader reader, ulong size)
        {
            var result = new SampleSizeBox();

            ReadFullBox(reader, result);

            result.ConstantSize = reader.ReadUInt32();
            var count = reader.ReadUInt32();
            if (result.ConstantSize == 0)
            {
                for (int i = 0; i < count; i++)
                {
                    result.SizeTable.Add(reader.ReadUInt32());
                }
            }

            return result;
        }

        internal static MovieExtendsBox ReadMovieExtendsBox(BoxBinaryReader reader, ulong size)
        {
            var result = new MovieExtendsBox();

            var offset = reader.Offset;

            ReadContainerBox(reader, result, size - (reader.Offset - offset));

            return result;
        }

        internal static TrackExtendsBox ReadTrackExtendsBox(BoxBinaryReader reader, ulong size)
        {
            var result = new TrackExtendsBox();

            ReadFullBox(reader, result);

            result.TrackId = reader.ReadUInt32();
            result.SampleDescriptionIndex = reader.ReadUInt32();
            result.SampleDuration = reader.ReadUInt32();
            result.SampleSize = reader.ReadUInt32();
            result.SampleFlags = new SampleFlags(reader.ReadUInt32());

            return result;
        }

        internal static SampleDescriptionBox ReadSampleDescriptionBox(BoxBinaryReader reader, ulong size)
        {
            var result = new SampleDescriptionBox();

            var offset = reader.Offset;

            ReadFullBox(reader, result);

            var count = reader.ReadUInt32(); // not needed
            ReadContainerBox(reader, result, size - (reader.Offset - offset));

            return result;
        }

        internal static DfxpBox ReadDfxpBox(BoxBinaryReader reader, ulong size)
        {
            var result = new DfxpBox();

            reader.ReadBytes(6); // reserved
            result.DataReferenceIndex = reader.ReadUInt16();

            return result;
        }

        internal static SubtitleSampleEntryBox ReadSubtitleSampleEntryBox(BoxBinaryReader reader, ulong size)
        {
            var result = new SubtitleSampleEntryBox();

            var offset = reader.Offset;

            reader.ReadBytes(6); // reserved
            result.DataReferenceIndex = reader.ReadUInt16();

            result.Namespace = reader.ReadNullTerminatedString();
            result.SchemaLocation = reader.ReadNullTerminatedString();
            result.ImageMimeType = reader.ReadNullTerminatedString();

            ReadContainerBox(reader, result, size - (reader.Offset - offset));

            return result;
        }

        internal static AudioSampleEntryBox ReadAudioSampleEntryBox(BoxBinaryReader reader, ulong size)
        {
            var result = new AudioSampleEntryBox();

            var offset = reader.Offset;

            reader.ReadBytes(6); // reserved
            result.DataReferenceIndex = reader.ReadUInt16();
            result.AudioEncodingVersion = reader.ReadUInt16();
            result.AudioEncodingRevisionLevel = reader.ReadUInt16();
            result.AudioEncodingVendor = reader.ReadString(4);
            result.ChannelCount = reader.ReadUInt16();
            result.SampleSize = reader.ReadUInt16();
            result.AudioCompressionId = reader.ReadUInt16();
            result.AudioPacketSize = reader.ReadUInt16();
            result.SampleRate = reader.ReadUInt32();

            ReadContainerBox(reader, result, size - (reader.Offset - offset));

            return result;
        }

        internal static VisualSampleEntryBox ReadVisualSampleEntryBox(BoxBinaryReader reader, ulong size)
        {
            var result = new VisualSampleEntryBox();

            var offset = reader.Offset;

            var r1 = reader.ReadBytes(6); // reserved
            result.DataReferenceIndex = reader.ReadUInt16();

            var p1 = reader.ReadUInt16(); // predefined
            var r2 = reader.ReadUInt16(); // reserved
            var p2 = reader.ReadBytes(12); // predefined
            result.Width = reader.ReadUInt16();
            result.Height = reader.ReadUInt16();
            result.HorizResolution = reader.ReadUInt32();
            result.VertResolution = reader.ReadUInt32();
            var r3 = reader.ReadUInt32(); // reserved
            result.FrameCount = reader.ReadUInt16();

            byte count = reader.ReadByte();
            result.CompressorName = reader.ReadString(count);
            reader.ReadBytes(0x20 - 1 - count); // 32 bytes are reserved whether we use them or not.
            result.Depth = reader.ReadUInt16();
            var p3 = reader.ReadInt16(); //predefined (-1)

            ReadContainerBox(reader, result, size - (reader.Offset - offset));

            return result;
        }

        internal static AdvancedVideoCodingBox ReadAdvancedVideoCodingBox(BoxBinaryReader reader, ulong size)
        {
            var result = new AdvancedVideoCodingBox();

            result.ConfigurationVersion = reader.ReadByte();
            result.AvcProfileIndication = reader.ReadByte();
            result.AvcCompatibleProfiles = reader.ReadByte();
            result.AvcLevelIndication = reader.ReadByte();
            result.NaluLengthSize = (byte)(1 + (reader.ReadByte() & 0x03)); // the first 2 contain NAUL length size minus 1. The rest are reserved (111111)

            var numSequenceParameters = (byte)(reader.ReadByte() & 0x1f); // last 5 bytes have the value, the rest is reserved (111)
            for (uint i = 0U; i < numSequenceParameters; i++)
            {
                var length = reader.ReadUInt16();
                result.SequenceParameters.Add(reader.ReadBytes(length));
            }

            var numPictureParameters = reader.ReadByte();
            for (uint j = 0U; j < numPictureParameters; j++)
            {
                var length = reader.ReadUInt16();
                result.PictureParameters.Add(reader.ReadBytes(length));
            }

            return result;
        }

        internal static ElementaryStreamDescriptorBox ReadElementaryStreamDescriptorBox(BoxBinaryReader reader, ulong size)
        {
            var result = new ElementaryStreamDescriptorBox();

            var offset = reader.Offset;

            ReadFullBox(reader, result);

            result.StreamDescriptor = ReadDescriptor(reader, size - (reader.Offset - offset));

            return result;
        }

        internal static IDescriptor ReadDescriptor(BoxBinaryReader reader, ulong size)
        {
            var offset = reader.Offset;

            IDescriptor result = ReadSubDescriptor(reader);

            while (true)
            {
                var bytesRead = reader.Offset - offset;
                if (size == bytesRead) break;
                var subDescriptor = ReadSubDescriptor(reader);
                result.SubDescriptors.Add(subDescriptor);
            }

            return result;
        }

        internal static IDescriptor ReadSubDescriptor(BoxBinaryReader reader)
        {
            IDescriptor result;

            var tag = (DescriptorTag)reader.ReadByte();

            uint payloadSize = 0;
            uint headerSize = 1;
            uint maxHeaderSize = 4;
            byte num;

            do
            {
                headerSize++;
                num = reader.ReadByte();
                payloadSize = (payloadSize << 7) + ((uint)(num & 0x7f));
            }
            while ((--maxHeaderSize != 0) && ((num & 0x80) != 0));

            switch (tag)
            {
                case DescriptorTag.ES:
                    result = ReadElementaryStreamDescriptor(reader);
                    break;
                case DescriptorTag.DECODER_CONFIG:
                    result = ReadDecoderConfigurationDescriptor(reader);
                    break;
                case DescriptorTag.DECODER_SPECIFIC_INFO:
                    result = ReadDecoderSpecificInformationDescriptor(reader, payloadSize);
                    break;
                case DescriptorTag.SL_CONFIG:
                    result = ReadSyncronizationConfigurationDescriptor(reader);
                    break;
                default:
                    throw new NotImplementedException();
            }

            result.PayloadSize = payloadSize;
            result.HeaderSize = headerSize;
            result.Tag = tag;

            return result;
        }

        internal static ElementaryStreamDescriptor ReadElementaryStreamDescriptor(BoxBinaryReader reader)
        {
            var result = new ElementaryStreamDescriptor();

            result.ElementaryStreamId = reader.ReadUInt16();
            var num = reader.ReadByte();
            result.Flags = (uint)((num >> 5) & 0x7);
            result.StreamPriority = (byte)(num & 0x1f);

            if ((result.Flags & 1) != 0)
            {
                result.DependsOn = reader.ReadUInt16();
            }
            else
            {
                result.DependsOn = 0;
            }

            if ((result.Flags & 2) != 0)
            {
                byte count = reader.ReadByte();
                if (count != 0)
                {
                    result.Url = reader.ReadString(count);
                }
            }

            if ((result.Flags & 2) != 0)
            {
                result.OcrElementaryStreamId = reader.ReadUInt16();
            }
            else
            {
                result.OcrElementaryStreamId = 0;
            }

            return result;
        }

        internal static DecoderConfigurationDescriptor ReadDecoderConfigurationDescriptor(BoxBinaryReader reader)
        {
            var result = new DecoderConfigurationDescriptor();

            result.ObjectTypeIndication = (DecoderObjectTypes)reader.ReadByte();
            byte num = reader.ReadByte();
            result.StreamType = (byte)((num >> 2) & 0x3f);
            result.UpStream = (num & 2) != 0;
            result.BufferSize = reader.ReadUInt24();
            result.MaxBitrate = reader.ReadUInt32();
            result.AverageBitrate = reader.ReadUInt32();

            return result;
        }

        internal static DecoderSpecificInformationDescriptor ReadDecoderSpecificInformationDescriptor(BoxBinaryReader reader, uint payloadSize)
        {
            var result = new DecoderSpecificInformationDescriptor();

            result.Information = reader.ReadBytes((int)payloadSize);

            return result;
        }

        internal static SyncronizationConfigurationDescriptor ReadSyncronizationConfigurationDescriptor(BoxBinaryReader reader)
        {
            var result = new SyncronizationConfigurationDescriptor();

            result.Predefined = reader.ReadByte();

            return result;
        }

        internal static MovieFragmentBox ReadMovieFragmentBox(BoxBinaryReader reader, ulong size)
        {
            var result = new MovieFragmentBox();

            var offset = reader.Offset;

            ReadContainerBox(reader, result, size - (reader.Offset - offset));

            return result;
        }

        internal static MovieFragmentHeaderBox ReadMovieFragmentHeaderBox(BoxBinaryReader reader, ulong size)
        {
            var result = new MovieFragmentHeaderBox();

            ReadFullBox(reader, result);

            result.SequenceNumber = reader.ReadUInt32();

            return result;
        }

        internal static TrackFragmentBox ReadTrackFragmentBox(BoxBinaryReader reader, ulong size)
        {
            var result = new TrackFragmentBox();

            var offset = reader.Offset;

            ReadContainerBox(reader, result, size - (reader.Offset - offset));

            return result;
        }

        internal static IndependentAndDisposableSamplesBox ReadIndependentAndDisposableSamplesBox(BoxBinaryReader reader, ulong size)
        {
            var result = new IndependentAndDisposableSamplesBox();

            var offset = reader.Offset;

            ReadFullBox(reader, result);

            while (size > reader.Offset - offset)
            {
                var sample = new IndependentAndDisposableSample();

                byte num = reader.ReadByte();
                sample.SampleDependsOn = (num & 0x30) >> 4;
                sample.SampleIsDependedOn = (num & 12) >> 2;
                sample.SampleHasRedundancy = num & 3;
                result.Samples.Add(sample);
            }

            return result;
        }

        internal static TrackFragmentHeaderBox ReadTrackFragmentHeaderBox(BoxBinaryReader reader, ulong size)
        {
            var result = new TrackFragmentHeaderBox();

            ReadFullBox(reader, result);

            result.TrackId = reader.ReadUInt32();
            if ((result.Flags & 1) != 0)
            {
                result.BaseDataOffset = reader.ReadUInt64();
            }
            if ((result.Flags & 2) != 0)
            {
                result.SampleDescriptionIndex = reader.ReadUInt32();
            }
            if ((result.Flags & 8) != 0)
            {
                result.DefaultSampleDuration = reader.ReadUInt32();
            }
            if ((result.Flags & 0x10) != 0)
            {
                result.DefaultSampleSize = reader.ReadUInt32();
            }
            if ((result.Flags & 0x20) != 0)
            {
                result.DefaultSampleFlags = new SampleFlags(reader.ReadUInt32());
            }

            return result;
        }

        internal static TrackFragmentRunBox ReadTrackFragmentRunBox(BoxBinaryReader reader, ulong size)
        {
            var result = new TrackFragmentRunBox();

            ReadFullBox(reader, result);

            var sampleCount = reader.ReadUInt32();

            if ((result.Flags & 0x1) != 0)
            {
                result.DataOffset = reader.ReadUInt32();
            }

            if ((result.Flags & 0x4) != 0)
            {
                result.FirstSampleFlags = new SampleFlags(reader.ReadUInt32());
            }

            for (int i = 0; i < sampleCount; i++)
            {
                var sample = new TrackFragmentRunSample();

                if ((result.Flags & 0x100) != 0)
                {
                    sample.SampleDuration = reader.ReadUInt32();
                }
                if ((result.Flags & 0x200) != 0)
                {
                    sample.SampleSize = reader.ReadUInt32();
                }
                if ((result.Flags & 0x400) != 0)
                {
                    sample.SampleFlags = new SampleFlags(reader.ReadUInt32());
                }
                if ((result.Flags & 0x800) != 0)
                {
                    sample.SampleCompositionTimeOffset = reader.ReadUInt32();
                }
                result.Samples.Add(sample);
            }

            return result;
        }

        internal static MovieFragmentRandomAccessBox ReadMovieFragmentRandomAccessBox(BoxBinaryReader reader, ulong size)
        {
            var result = new MovieFragmentRandomAccessBox();

            var offset = reader.Offset;

            ReadContainerBox(reader, result, size - (reader.Offset - offset));

            return result;
        }

        internal static SegmentIndexBox ReadSegmentIndexBox(BoxBinaryReader reader, ulong size)
        {
            var result = new SegmentIndexBox();

            ReadFullBox(reader, result);

            result.ReferenceId = reader.ReadUInt32();
            result.Timescale = reader.ReadUInt32();

            if (result.Version == 1)
            {
                result.EarliestPresentationTime = reader.ReadUInt64();
                result.FirstOffset = reader.ReadUInt64();
            }
            else
            {
                result.EarliestPresentationTime = reader.ReadUInt32();
                result.FirstOffset = reader.ReadUInt32();
            }

            var reserved = reader.ReadUInt16();
            var subSegmentCount = reader.ReadUInt16();

            for (int i = 0; i < subSegmentCount; i++)
            {
                var subsegment = new Subsegment();

                uint referenceNum = reader.ReadUInt32();
                subsegment.ReferenceType = System.Convert.ToBoolean(referenceNum >> 31); // 1 bit
                subsegment.ReferencedSize = (referenceNum << 1) >> 1; // 31 bits

                subsegment.Duration = reader.ReadUInt32();

                uint sapNum = reader.ReadUInt32();
                subsegment.StartsWithSAP = System.Convert.ToBoolean(sapNum >> 31); // 1 bit
                subsegment.SAPType = System.Convert.ToUInt16((sapNum << 1) >> 29); // 3 bits
                subsegment.SAPDeltaTime = (sapNum << 4) >> 4; // 28 bits

                result.Subsegments.Add(subsegment);
            }

            return result;
        }

        internal static ProtectionSystemSpecificHeaderBox ReadProtectionSystemSpecificHeaderBox(BoxBinaryReader reader, ulong size)
        {
            var result = new ProtectionSystemSpecificHeaderBox();

            var offset = reader.Offset;

            ReadFullBox(reader, result);

            result.SystemId = reader.ReadGuid();
            var length = reader.ReadUInt32();
            result.Data = reader.ReadBytes((int)length);

            return result;
        }

        internal static ProtectedSchemeInformationBox ReadProtectedSchemeInformationBox(BoxBinaryReader reader, ulong size)
        {
            var result = new ProtectedSchemeInformationBox();

            ReadContainerBox(reader, result, size);

            return result;
        }

        internal static SchemeInformationBox ReadSchemeInformationBox(BoxBinaryReader reader, ulong size)
        {
            var result = new SchemeInformationBox();

            ReadContainerBox(reader, result, size);

            return result;
        }

        internal static FreeSpaceBox ReadFreeSpaceBox(BoxBinaryReader reader, ulong size)
        {
            var result = new FreeSpaceBox();

            result.Size = (uint)reader.ReadBytes((int)size).Length;

            return result;
        }

        internal static FormatBox ReadFormatBox(BoxBinaryReader reader, ulong size)
        {
            var result = new FormatBox();

            result.Format = reader.ReadString(4);

            return result;
        }

        internal static SchemeTypeBox ReadSchemeTypeBox(BoxBinaryReader reader, ulong size)
        {
            var result = new SchemeTypeBox();

            ReadFullBox(reader, result);

            result.SchemeType = reader.ReadString(4);
            result.SchemeVersion = reader.ReadUInt32();
            if ((result.Flags & 0x01) == 0x01)
            {
                result.SchemeUri = reader.ReadNullTerminatedString();
            }

            return result;
        }

        internal static MovieExtendsHeaderBox ReadMovieExtendsHeaderBox(BoxBinaryReader reader, ulong size)
        {
            var result = new MovieExtendsHeaderBox();

            ReadFullBox(reader, result);

            if ((result.Version & 0x01) == 0x01)
            {
                result.FragmentDuration = reader.ReadUInt64();
            }
            else
            {
                result.FragmentDuration = reader.ReadUInt32();
            }

            return result;
        }

        internal static TrackFragmentBaseMediaDecodeTimeBox ReadTrackFragmentBaseMediaDecodeTimeBox(BoxBinaryReader reader, ulong size)
        {
            var result = new TrackFragmentBaseMediaDecodeTimeBox();

            ReadFullBox(reader, result);

            if ((result.Version & 0x01) == 0x01)
            {
                result.BaseMediaDecodeTime = reader.ReadUInt64();
            }
            else
            {
                result.BaseMediaDecodeTime = reader.ReadUInt32();
            }

            return result;
        }

        internal static TrackEncryptionBox ReadTrackEncryptionBox(BoxBinaryReader reader, ulong size)
        {
            var result = new TrackEncryptionBox();

            ReadFullBox(reader, result);

            result.DefaultIsEntrypted = reader.ReadUInt24();
            result.DefaultIVSize = reader.ReadByte();
            result.DefaultKID = reader.ReadGuid();

            return result;
        }

        internal static TrackFragmentRandomAccessBox ReadTrackFragmentRandomAccessBox(BoxBinaryReader reader, ulong size)
        {
            var result = new TrackFragmentRandomAccessBox();

            ReadFullBox(reader, result);

            result.TrackId = reader.ReadUInt32();

            uint num = reader.ReadUInt32();
            var reserved = ((int)(num >> 6)) & 0x3ffffff;
            result.LengthSizeOfTrafNum = (byte)((num >> 4) & 3);
            result.LengthSizeOfTrunNum = (byte)((num >> 2) & 3);
            result.LengthSizeOfSampleNum = (byte)(num & 3);

            var entryCount = reader.ReadUInt32();
            for (int i = 0; i < entryCount; i++)
            {
                var entry = new TrackFragmentRandomAccessEntry();

                if (result.Version == 1)
                {
                    entry.Time = reader.ReadUInt64();
                    entry.MoofOffset = reader.ReadUInt64();
                }
                else
                {
                    entry.Time = reader.ReadUInt32();
                    entry.MoofOffset = reader.ReadUInt32();
                }

                entry.TrafNumber = reader.ReadIntValueFromBytes(result.LengthSizeOfTrafNum);
                entry.TrunNumber = reader.ReadIntValueFromBytes(result.LengthSizeOfTrunNum);
                entry.SampleNumber = reader.ReadIntValueFromBytes(result.LengthSizeOfSampleNum);

                result.TrackFragmentRandomAccessEntries.Add(entry);
            }

            return result;
        }

        internal static MediaDataBox ReadMediaDataBox(BoxBinaryReader reader, ulong size)
        {
            var result = new MediaDataBox();

            result.Data = reader.ReadBytes((int)size);

            return result;
        }

        internal static UnknownBox ReadUnknownBox(BoxBinaryReader reader, ulong size)
        {
            var result = new UnknownBox();

            result.Data = reader.ReadBytes((int)size);

            return result;
        }
    }
}
